package terrain;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.OutputStream;

import javax.microedition.khronos.opengles.GL10;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
	
public abstract class Terrain {
	protected	HEIGHT_DATA 	m_HeightData;	// the height data
	protected float 			m_fScaleHeight = 0.08f;		// the scale variable for height value
	protected float 			m_fScalePosition = 0.8f;
	
	protected int					m_iWidth;
	protected int 					m_iHeight;
	protected int 				m_iHalfWidth;
	protected int					m_iHalfHeight;
	
	protected float 			m_fTranslateX = 0f;
	protected float 			m_fTranslateY = 0f;
	protected float 			m_fTranslateZ = 0f;
	
	protected TerrainTexture 		m_texture = null;
	
	// Center of terrain wa
	public abstract void Render (GL10 gl);
	
	// ----------------------------------------
	// Name:			LoadHeightMapFromRawFile - public
	// Description:		load the height-map from a input stream with specific size
	// Arguments:		+ is: the input stream
	//					+ TerrainWidth : the width of terrain
	//					+ TerrainHeight : the height of terrain
	// Return:			true if save OK
	//-----------------------------------------
	public boolean LoadHeightMapFromRawFile (InputStream is, int TerrainWidth, int TerrainHeight){
		if (m_HeightData != null && m_HeightData.m_aData != null){
			UnloadHeightMap();
		}
		
		m_iWidth = TerrainWidth;
		m_iHeight = TerrainHeight;
		m_iHalfWidth = m_iWidth >> 1;
		m_iHalfHeight = m_iHeight >> 1;
		
		m_HeightData = new HEIGHT_DATA();
		try {
			
			byte[] data = new byte[m_iWidth*m_iHeight];
			m_HeightData.m_aData = new short[m_iWidth*m_iHeight];
			if (m_HeightData.m_aData == null){
				return false;
			}
			is.read(data, 0, m_iWidth*m_iHeight);
			for (int i = 0; i < m_iWidth*m_iHeight; ++i){
				m_HeightData.m_aData[i] = (short)(data[i] >= 0 ? data[i] : 256 + data[i]);
			}
		}
		catch (IOException e) {
			// process exception
		}
		finally {			//Always clear and close
			is = null;
		}
		return true;
	}
	
	// ----------------------------------------
	// Name:			LoadHeightMapFromBitmap - public
	// Description:		load the height-map from a input stream with specific size
	// Arguments:		+ is: the input stream
	//					+ TerrainWidth : the width of terrain
	//					+ TerrainHeight : the height of terrain
	// Return:			true if save OK
	//-----------------------------------------
	public boolean LoadHeightMapFromBitmap (InputStream is, int TerrainWidth, int TerrainHeight){
		if (m_HeightData != null && m_HeightData.m_aData != null){
			UnloadHeightMap();
		}
		Bitmap bitmap = null;
		m_iWidth = TerrainWidth;
		m_iHeight = TerrainHeight;
		m_iHalfWidth = m_iWidth >> 1;
		m_iHalfHeight = m_iHeight >> 1;
		
		m_HeightData = new HEIGHT_DATA();
		try {
			bitmap = BitmapFactory.decodeStream(is);
			int[] data = new int[m_iWidth*m_iHeight];
			m_HeightData.m_aData = new short[m_iWidth*m_iHeight];
			if (m_HeightData.m_aData == null){
				return false;
			}
			//is.read(data, 0, m_iWidth*m_iHeight);
			bitmap.getPixels(data, 0, m_iWidth, 0, 0, m_iWidth, m_iHeight);
			short temp;
			for (int z = 0; z < m_iHeight; ++z){
				for (int x = 0; x < m_iWidth; ++x){
					temp = (short)((bitmap.getPixel(x, z) >> 16) & 0x000000FF);
					m_HeightData.m_aData[z * m_iHeight + x] = (short)(temp >= 0 ? temp : 256 + temp);
				}
			}
		}
		catch (Exception e) {
			// process exception
		}
		finally {			//Always clear and close
			is = null;
		}
		return true;
	}
	
	// ----------------------------------------
	// Name:			LoadHeightMapFromBitmap - public
	// Description:		load the height-map from a bitmap stream without specific size
	// Arguments:		+ is: the input stream
	// Return:			true if save OK
	//-----------------------------------------
	public boolean LoadHeightMapFromBitmap (InputStream is){
		if (m_HeightData != null && m_HeightData.m_aData != null){
			UnloadHeightMap();
		}
		Bitmap bitmap = null;
		
		
		m_HeightData = new HEIGHT_DATA();
		try {
			bitmap = BitmapFactory.decodeStream(is);
			m_iWidth = bitmap.getWidth();
			m_iHeight = bitmap.getHeight();
			m_iHalfWidth = m_iWidth >> 1;
			m_iHalfHeight = m_iHeight >> 1;
			
			m_HeightData.m_aData = new short[m_iWidth*m_iHeight];
			if (m_HeightData.m_aData == null){
				return false;
			}
			short temp;
			for (int z = 0; z < m_iHeight; ++z){
				for (int x = 0; x < m_iWidth; ++x){
					temp = (short)((bitmap.getPixel(x, z) >> 16) & 0x000000FF);
					m_HeightData.m_aData[z * m_iHeight + x] = (short)(temp >= 0 ? temp : 256 + temp);
				}
			}
		}
		catch (Exception e) {
			// process exception
		}
		finally {			//Always clear and close
			is = null;
		}
		return true;
	}
	
	// ----------------------------------------
	// Name:			SaveHeightMap - public
	// Description:		Save current height-map to a output stream
	// Arguments:		os: the output stream
	// Return:			true if save OK
	//-----------------------------------------
	public boolean SaveHeightMap (OutputStream os){
		// save data to outputStream
		return true;
	}
	
	// ----------------------------------------
	// Name:			UnloadHeightMap - public
	// Description:		Unloading the height-map
	// Arguments:		None
	// Return:			true if unload OK
	//-----------------------------------------
	public boolean UnloadHeightMap (){
		if (m_HeightData != null && m_HeightData.m_aData != null){
			m_HeightData.m_aData = null;
		}
		m_HeightData = null;
		return true;
	}
	
	// ----------------------------------------
	// Name:			SetHeightScale - public
	// Description:		Set the height scaling variable
	// Arguments:		+ Scale: new Scaling variable
	// Return:			None
	//-----------------------------------------
	public  void SetHeightScale (float Scale){
		m_fScaleHeight = Scale;
	}
	
	// ----------------------------------------
	// Name:			SetPositionScale - public
	// Description:		Set the height scaling variable
	// Arguments:		+ Scale: new Scaling variable
	// Return:			None
	//-----------------------------------------
	public  void SetPositionScale (float Scale){
		m_fScalePosition = Scale;
	}
	
	public float GetPositionScale(){
		return m_fScalePosition;
	}
	
	// ----------------------------------------
	// Name:			SetHeightAtPoint - public
	// Description:		Set the true height value at the given point
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	//					+ Height:	the new height value of given point
	// Return:			None
	//-----------------------------------------
	public  void SetHeightAtPoint (int X, int Z, short Height){
		X += m_iHalfWidth;
		Z += m_iHalfHeight;
		this.m_HeightData.m_aData[Z* m_iHeight + X] = Height;
	}
	
	// ----------------------------------------
	// Name:			GetTrueHeightAtPoint - public
	// Description:		Set the true height value at the given point
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	// Return:			the real height at given point(x,z)
	//-----------------------------------------
	public float GetTrueHeightAtPoint (int X, int Z){
		X += m_iHalfWidth;
		Z += m_iHalfHeight;
		return this.m_HeightData.m_aData[Z* m_iHeight + X];
	}
	
	// ----------------------------------------
	// Name:			GetScaleHeightAtPoint - public
	// Description:		Set the true height value at the given point
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	// Return:			the scale height at given point(x,z) to suit with the screen
	//-----------------------------------------
	public float GetScaleHeightAtPoint (int X, int Z){
		X += m_iHalfWidth;
		Z += m_iHalfHeight;
		return (this.m_HeightData.m_aData[Z* m_iHeight + X])* m_fScaleHeight;
	}
	
	// ----------------------------------------
	// Name:			MoveToLeft - public
	// Description:		Move the terrain to left side
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	// Return:			None
	//-----------------------------------------
	public void MoveToLeft (){
		m_fTranslateX += 1f; 
	}
	
	// ----------------------------------------
	// Name:			MoveToRight - public
	// Description:		Move the terrain to right side
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	// Return:			None
	//-----------------------------------------
	public void MoveToRight (){
		m_fTranslateX -= 1f; 
	}
	
	// ----------------------------------------
	// Name:			MoveToFront - public
	// Description:		Move the terrain to front side
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	// Return:			None
	//-----------------------------------------
	public void MoveToFront (){
		m_fTranslateZ += 1f; 
	}
	
	
	// ----------------------------------------
	// Name:			MoveToBack - public
	// Description:		Move the terrain to back side
	// Arguments:		+ X,Z : 	Position X and Z (similar to position of pixel)
	// Return:			None
	//-----------------------------------------
	public void MoveToBack (){
		m_fTranslateZ -= 1f; 
	}
	
	public void MakeSmooth(float k){
//		float smooth_factor[] = new float[9];
//		for (int i = 0; i < smooth_factor.length; ++i){
//			smooth_factor[i] = k;
//		}
		float temp;
		
		// right to left
		for (int x = 1 - m_iHalfWidth ; x < m_iHalfWidth; ++x){
			for (int z = -m_iHalfHeight; z < m_iHalfHeight; ++z){
				temp = (GetTrueHeightAtPoint(x, z)*k + (1-k)*GetTrueHeightAtPoint(x - 1, z));
				SetHeightAtPoint(x, z, (short)temp);
			}
		}
		
		// right to left
		for (int x = m_iHalfWidth - 2; x >= -m_iHalfWidth; --x){
			for (int z = -m_iHalfHeight; z < m_iHalfHeight; ++z){
				temp = (GetTrueHeightAtPoint(x, z)*k + (1-k)*GetTrueHeightAtPoint(x + 1, z));
				SetHeightAtPoint(x, z, (short)temp);
			}
		}
		
		// top to bottom
		for (int z = 1 - m_iHalfHeight; z < m_iHalfHeight; ++z){
			for (int x = -m_iHalfWidth; x < m_iHalfWidth; ++x){
				temp = (GetTrueHeightAtPoint(x, z)*k + (1-k)*GetTrueHeightAtPoint(x , z - 1));
				SetHeightAtPoint(x, z, (short)temp);
			}
		}
		
		// top to bottom
		for (int z = m_iHalfHeight - 2; z >= -m_iHalfHeight; --z){
			for (int x = -m_iHalfWidth; x < m_iHalfWidth; ++x){
				temp = (GetTrueHeightAtPoint(x, z)*k + (1-k)*GetTrueHeightAtPoint(x , z + 1));
				SetHeightAtPoint(x, z, (short)temp);
			}
		}
		
	}
	
	public void SetTexture(InputStream texture){
		m_texture = new TerrainTexture(texture);
	}
	
	public int GetWidth (){
		return m_iWidth;
	}
	
	public int GetHeight (){
		return m_iHeight;
	}
	
	public float GetScaleX(int X){
		return (X + m_iHalfWidth)*m_fScalePosition;
	}
	
	public float GetScaleZ(int Z){
		return (Z + m_iHalfWidth)*m_fScalePosition;
	}
}